﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
using System.Threading;
using System.Text;
using System.Net;
using System.Net.Sockets;
using Pb;
using Google.Protobuf;
using System.IO;

using System.Linq;

public class SocketManager : MonoBehaviour {


    #region noneed
     

    private readonly string UNITY_CLIENT_SOCKET_DISSCONNECT = "UNITY_CLIENT_SOCKET_DISSCONNECT";
	private const string UNITY_CLIENT_SOCKET_ONDISSCONNECT = "UNITY_CLIENT_SOCKET_ONDISSCONNECT";
    //private const string UNITY_CLIENT_SOCKET_ONDISSCONNECT = "!!! ";

    private static bool applicationIsQuitting = false;
    private static SocketManager _instance;
	public static SocketManager Instance {
		get {


			if(!_instance) {
				_instance = GameObject.FindObjectOfType(typeof(SocketManager)) as SocketManager;
				if(!_instance) {
                    if (applicationIsQuitting) return null;
					GameObject container = new GameObject();
                    container.name = "SocketManagerContainer";
					_instance = container.AddComponent(typeof(SocketManager)) as SocketManager; 
				}
			}
            
			return _instance;
		}
	}
	
	public SocketListener socketListener;
	
	private SocketConnect socketConnect;
    public bool isConnect = false;
    private byte[] messageList ;
    private string m_LastDisconnectInfo = string.Empty;
	
	// Use this for initialization
	void Start () {
		DontDestroyOnLoad(this);
		socketConnect = SocketConnect.Instance;
		socketConnect.SetReceiveMessageDelegate(new SocketConnect.DelegateReceiveMessage(SocketReceiveMessage));
		socketConnect.SetDelegateDisconnect(new SocketConnect.DelegateDisconnect(SocketDisconnect));
		socketConnect.onSocketDisconnectAction = OnSocetDisconnectHandler;
    }
    #endregion

    // shawn.debug
    // UI operations must be placed in the main thread, so by the Update method to switch to the main thread.
    // TODO: Looking for a better way to achieve this function.
    // PS: "disconnectDelegate" is on a background thread.
    void Update () {
        //if (socketListener != null && socketConnect.buffer != null && socketConnect.TempMsgID != MsgId.HeartBeatRequest&& socketConnect.listByte.Count>=0)
       if (socketListener != null && socketConnect.listByte.Count>0)
       {
            byte[] tempByte= socketConnect.listByte[0];
            socketConnect.listByte.RemoveAt(0);
            //socketListener.SocketReceiveMessage(socketConnect.buffer);
            socketListener.SocketReceiveMessage(tempByte);
            //socketConnect.buffer = null;
            socketConnect.TempMsgID = 0;
        }
    }

    void OnDestroy()
    {
        socketListener = null;
        Disconnect("Application quit");//false
        applicationIsQuitting = true;
    }

    /* ------ Socket Methods ------ */
    public void Connect(SocketConnectInfo info, string ipStr = null ,bool force = true) {
		//messageList.Clear();
		socketConnect.Connect(info, ipStr,force);
	}
    public void ConnectTest(SocketConnectInfo info)
    {
        socketConnect.ConnectTest(info);
    }
    
    public void Disconnect(string info) {
		//messageList.Clear();
		if(socketConnect != null) { socketConnect.Disconnect(info); }//false
	}
    
    public void SendProtobuf1<T>(T t,int MsgId) where T:IMessage
    {
 
        ByteBuffer b = new ByteBuffer();
        MemoryStream ms = new MemoryStream();
        t.WriteTo(ms);
        byte[] bytes = ms.ToArray();
        
        b.WriteShort((ushort)(bytes.Length + 2));
        //byte[] z = b.ToBytes();
        //Debug.Log(z.Length);
        b.WriteShort((ushort)MsgId);
        b.WriteBytes(bytes);
        byte[] msg = b.ToBytes();
        socketConnect.SendProtobuf(msg);
    }
    

    public void SendPackage(string message) {
		socketConnect.SendPackage(message);
	}
	public void SendPackageWithJson(JSONObject messageObj) {
		socketConnect.SendPackageWithJson(messageObj);
	}
	
	/* ------ Socket Delegate ------ */
	private void SocketReceiveMessage (byte[] message) {
		
	}
	
	private void SocketDisconnect (string disconnectInfo) {
		
	}
	
	// shawn.debug
	public void SocketMessageRollback (string message) {
		
	}

    private void OnSocetDisconnectHandler()
    {
        if (socketListener != null) socketListener.OnSocketDisconnect(m_LastDisconnectInfo);
    }
    public void Reconcent()
    {
        GameObject NetWorkAbnormal = NGUITools.AddChild(GameObject.Find("UI Root").gameObject, (GameObject)(Resources.Load("NetWorkAbnormal")));
    }

    /* ------------------------------------------------------------------------------------------------ */
    /* ----------- SocketConnect Class ----------- */
    /* ------------------------------------------------------------------------------------------------ */
    private class SocketConnect {
        private static object lockObj = new object();
		public delegate void DelegateReceiveMessage(byte[] message);
		public delegate void DelegateDisconnect(string disconnectInfo);

        private static SocketConnect _instance;
		public static SocketConnect Instance {
			get {
				if(_instance == null) {
					_instance = new SocketConnect();
				}
				return _instance;
			}
		}
		
		private Socket clientSocket;
		private Thread receiveThread;	// Receive message thread
		private Thread heartThread;		// Socket keep live thread
		private bool m_IsReceiveThreadRun;
		private bool m_IsheartThreadRun;

        
		public byte[] buffer = null;
        public List<byte[]> listByte = new List<byte[]>();
        public int TempMsgID;
		private int token = 0;
		private readonly int socketTimeout = 8;
		private readonly int heartInterval = 10000;
		private Queue packageQueue = Queue.Synchronized(new Queue());
		private DelegateReceiveMessage receiveMessageDelegate;
		private DelegateDisconnect disconnectDelegate; //重连不放在disconnectDelegate的原因是Disconnect方法在多处都会被调用,导致重连会发生多次调用的bug
        public System.Action onSocketDisconnectAction;//用于重连

        class SimpleAsyncState
        {
            public bool _IsTimeOut;
        }

//		private bool m_IsConnectSuccess;//BeginConnect 连接标识
		
		public void Connect(SocketConnectInfo info,string ipStr, bool force = true) {//force: true 强制重连,会把已连接的断开, false 不强制,已连接是不重现连接
            //Debug.Log
            Debug.Log("1 Socket: Create new connect: id: " + info.roomId + " host:" + info.roomHost + " port: " + info.roomPort + " db: " + info.roomDBName);
            //UnityEngine.Debug.Log("CK : ------------------------------ ipStr = " + ipStr + ", platform gameUrl = " + PlatformGameDefine.playform.gameUrl);

            if (!force && clientSocket != null && clientSocket.Connected) return;
            
            DoDisconnect();//false

            lock (lockObj)
            {
                clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                try
                {
                    char[] splS = ":".ToCharArray();
                    string[] ipArr = ipStr.Split(splS);
                    info.roomHost = ipArr[0];
                    info.roomPort = ipArr[1];
                    Debug.Log("2 Socket: Real connect: host:" + ipArr[0] + " port: " + ipArr[1]);
                }
                catch (Exception e) { }

                
                IPAddress ipAddress = null;// IPAddress.Parse(info.roomHost);
                if (IPAddress.TryParse(info.roomHost, out ipAddress)) { }
                else ipAddress = Dns.GetHostEntry(info.roomHost).AddressList[0];

                IPEndPoint ipEndpoint = new IPEndPoint(ipAddress, int.Parse(info.roomPort));               
                IAsyncResult result = clientSocket.BeginConnect(ipEndpoint, new AsyncCallback(ConnectCallback), new SimpleAsyncState());
                SocketManager.Instance.StartCoroutine(DoConnectTimeOut(result));
            }
		}

        public void ConnectTest(SocketConnectInfo info, string ipStr = null) {
            lock (lockObj)
            {
                clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                try
                {
                    char[] splS = ":".ToCharArray();
                    string[] ipArr = ipStr.Split(splS);
                    info.roomHost = ipArr[0];
                    info.roomPort = ipArr[1];
                    Debug.Log("2 Socket: Real connect: host:" + ipArr[0] + " port: " + ipArr[1]);
                }
                catch (Exception e) { }


                IPAddress ipAddress = null;// IPAddress.Parse(info.roomHost);
                if (IPAddress.TryParse(info.roomHost, out ipAddress)) { }
                else ipAddress = Dns.GetHostEntry(info.roomHost).AddressList[0];

                IPEndPoint ipEndpoint = new IPEndPoint(ipAddress, int.Parse(info.roomPort));
                IAsyncResult result = clientSocket.BeginConnect(ipEndpoint, new AsyncCallback(ConnectCallback), new SimpleAsyncState());
                SocketManager.Instance.StartCoroutine(DoConnectTimeOut(result));
            }
        }

		IEnumerator DoConnectTimeOut(IAsyncResult result,string message = null){
            float timeOut = socketTimeout;
			while ((timeOut -= Time.deltaTime) > 0) {
				if(result.IsCompleted) yield break;
				yield return 0;
			}

			if (message == null) message = "Connect time out";
            UnityEngine.Debug.Log("CK : ------------------------------ time out = " + message);
            SimpleAsyncState state = (SimpleAsyncState)result.AsyncState;
            state._IsTimeOut = true;
            Disconnect(message);//true
		}
		
		public void Disconnect(string info) {
            //PlatformGameDefine.playform.swithGameHostUrl();//切换游戏的IP
            //if (clientSocket != null) info = UNITY_CLIENT_SOCKET_ONDISSCONNECT + info;
            Debug.Log("3 Socket: Disconnect reason: " + info);
            DoDisconnect();

			if(disconnectDelegate != null) {
				disconnectDelegate(info);
			}
			

            
        }
        private void Disconnect() {
			Disconnect("Unknow");//false
		}
		private void DoDisconnect () {
            lock (lockObj)
            {
			    if(clientSocket != null) {
				    if (clientSocket.Connected) {
    //					clientSocket.Shutdown(SocketShutdown.Both); // shawn.debug Maybe Error
    //					clientSocket.Close();
					    clientSocket.Disconnect(false);
				    }
				    clientSocket = null;
                    NetWorkAbnormalMessage MR = new Pb.NetWorkAbnormalMessage { ReconectType=RECONECT_TYPE.LoginReconect};
                    Debug.Log("Reconnect:" + MR);
                    ByteBuffer b = new ByteBuffer();
                    MemoryStream ms = new MemoryStream();
                    MR.WriteTo(ms);
                    byte[] bytes = ms.ToArray();
                    b.WriteShort((ushort)(bytes.Length + 2));
                    b.WriteShort((ushort)(MsgId.NetWorkAbnormalMessage));
                    b.WriteBytes(bytes);
                    byte[] msg = b.ToBytes();

                    listByte.Add(msg);//这里发送消息是因为StartCoroutine(等协程相关方法) 需要在主线程中进行
                    //生成重新连接预设
                    //SocketManager.Instance. Reconcent();
                }
            }
			if(receiveThread != null && receiveThread.IsAlive) {
//				receiveThread.Abort();
				m_IsReceiveThreadRun = false;
			}
			if(heartThread != null && heartThread.IsAlive) {
//				heartThread.Abort();
				m_IsheartThreadRun = false;
			}
			packageQueue.Clear();
			this.buffer = null;
			this.token = 0;
		}

        
        #region 重连相关
        private Coroutine m_OnDisconnect_Coroutine;
        IEnumerator DoCheckOnSocketDisconnect()
        {
            float m_curDisConnectTime = 0;
            while (m_curDisConnectTime < 1)//采用这种方法的原因是为了解决异步线程导致的clientSocket == null 不可控问题. //同时可以解决连不上的时候高频率的重连问题
            {
                m_curDisConnectTime += Time.deltaTime;
                yield return 0;
                if (clientSocket != null && clientSocket.Connected) yield break;//在m_curDisConnectTime达到最大值前已经连上,就不用重连
            }

            if (onSocketDisconnectAction != null) onSocketDisconnectAction();
            m_OnDisconnect_Coroutine = null;
        }

        public void CheckOnSocketDisconnect()
        {
            if (m_OnDisconnect_Coroutine != null) SocketManager.Instance.StopCoroutine(m_OnDisconnect_Coroutine);//防止发生多次调用
            m_OnDisconnect_Coroutine = SocketManager.Instance.StartCoroutine(DoCheckOnSocketDisconnect());
        }
        #endregion 重连相关

        /* ------ Socket Methods ------ */
        private void ConnectCallback(IAsyncResult asyncConnect) {
            SimpleAsyncState state = (SimpleAsyncState) asyncConnect.AsyncState;
            if (state._IsTimeOut) return;

            if (asyncConnect.IsCompleted && clientSocket != null && clientSocket.Connected) {
				m_IsReceiveThreadRun = true;
				receiveThread = new Thread (new ThreadStart (ReceiveDataProtobuf));
				receiveThread.IsBackground = true;
				receiveThread.Start ();
			} else {
                Disconnect("asyncConnect is not completed");//true        端口不对模拟网络连接不上后 三次连接
            }
            SocketManager socketManager = SocketManager.Instance;
            Debug.Log("4 Socket: Connect success");
            if (asyncConnect.IsCompleted&&clientSocket.Connected && clientSocket!=null) {
                //发送chickin
                Debug.Log("ready send");
                int Cvi = 1;
                ulong Tt = TimeS.ConvertDateTimeInt(System.DateTime.Now);
                CheckinRequest cr = new CheckinRequest { Timestamp = Tt, ClientVerId = Cvi };
                socketManager.SendProtobuf1(cr, MsgId.CheckinRequest);
                socketManager. isConnect = true;
            }
            else
            {
                socketManager. isConnect = false;
            }
            
        }
		
		private void SendCallback(IAsyncResult asyncSend) {
		    //Debug.Log("Socket: Send success");
		}
        /*---接收消息----*/
       
       
        private void ReceiveDataProtobuf()
        {
            while (m_IsReceiveThreadRun)
            {
                if (!clientSocket.Connected)
                {
                    Disconnect("Connection Lost");
                    break;
                }
                try
                {
                    byte[] tokenbytes = new byte[4096];
                    
                    int len = clientSocket.Receive(tokenbytes);
                   
                    if (len <= 0)
                    {
                        Disconnect("Read Failed!");   //服务器关闭   //用户异地登录
                        break;
                    }
                    else
                    {
                        #region socket处理接收的信息
                        byte[] tokendata = new byte[len];
                        Array.Copy(tokenbytes, tokendata, len);
                        this.buffer = tokendata;
                        if (buffer != null)
                        {
                            //Debug.Log("测试"+this.buffer.Length);
                            byte[] dataLength = buffer.Skip(0).Take(2).ToArray();
                            byte[] dataId = buffer.Skip(2).Take(2).ToArray();
                            TempMsgID = ProtoDataToMsg.Byte2ushort(dataId);
                            Debug.Log("消息ID" + TempMsgID);
                            int FirstLen = ProtoDataToMsg.Byte2ushort(dataLength) + 2;
                            //Debug.Log("FirstLen:" + FirstLen + "buffer.Length" + buffer.Length);

                            if (FirstLen == buffer.Length)
                            {

                                if (TempMsgID == MsgId.HeartBeatRequest)
                                {
                                    Debug.Log("心跳" + 1111);
                                    SendHeartPackage(buffer);
                                }
                                else if (TempMsgID == MsgId.UserKickOutMessage)
                                {         //异地登录
                                    byte[] dataConcent = buffer.Skip(buffer.Length - (buffer.Length - 4)).ToArray();
                                    Debug.Log("异地登录");
                                    Pb.UserKickOutMessage newpb = Pb.UserKickOutMessage.Parser.ParseFrom(dataConcent);
                                    EginUser.Instance.userKickOutMessage = newpb;
                                }
                                else if (TempMsgID == MsgId.PingRequest)
                                {
                                    //ping
                                    SendPing(buffer);
                                }
                                else
                                {
                                    listByte.Add(this.buffer);
                                }
                            }
                            else
                            {
                                while (FirstLen != buffer.Length)
                                {
                                    byte[] tempByte1 = buffer.Skip(0).Take(FirstLen).ToArray();
                                    byte[] tempByte2 = buffer.Skip(FirstLen).ToArray();
                                    byte[] dataLength2 = tempByte2.Skip(0).Take(2).ToArray();
                                    FirstLen = ProtoDataToMsg.Byte2ushort(dataLength2) + 2;
                                    buffer = tempByte2;
                                    listByte.Add(tempByte1);
                                    if (FirstLen == buffer.Length)
                                    {
                                        listByte.Add(tempByte2);
                                        break;
                                    }
                                }
                                
                            }
                        }
                        else {
                            Debug.Log("this.buffer == null import!");
                            Debug.Log(len);
                            Debug.Log(tokenbytes);
                            Debug.Log(tokendata);
                        }
                        #endregion socket处理接收的信息 
                    }
                }
                catch (Exception e)
                {
                    Debug.Log("6 Read Error: " + e); //断开网线过一会收到
                    Disconnect("Socket_UnexpectedDisconnect");
                    break;
                }
            }
        }

        
        void ProcessPackageP() {
            receiveMessageDelegate(buffer);
        }

        private void ReceiveData() {
			
		}



        public void SendProtobuf(byte[] msg) {
             //clientSocket.SendBufferSize = 655536;
             IAsyncResult asyncSend = clientSocket.BeginSend(msg, 0, msg.Length, SocketFlags.None, new AsyncCallback(SendCallback), clientSocket);
        }
        public String BytesToint8String(byte[] byteArray)
        {
            StringBuilder strB = new StringBuilder();

            for (int i = 0; i < byteArray.Length; i++)
            {
                if (strB.Length > 0)
                {
                    strB.Append(" ");
                }
                strB.Append((ushort)byteArray[i]);
            }
            return strB.ToString();
        }
        public void SendPackage(string str) {
		
		}
		public void SendPackageWithJson(JSONObject messageObj) {
			SendPackage(messageObj.ToString());
		}

        public void SendPro() { 
            
        }
		/* ------ 发送心跳包 ------ */
		private void SendHeartPackage(byte[] data) {
			try {
                
                byte[] dataLength = data.Skip(0).Take(2).ToArray();
                byte[] dataId = data.Skip(2).Take(2).ToArray();
                byte[] dataConcent = data.Skip(data.Length - (data.Length - 4)).ToArray();
                int TempMsgID1 = ProtoDataToMsg.Byte2ushort(dataId);
                Pb.HeartBeatRequest newpb = Pb.HeartBeatRequest.Parser.ParseFrom(dataConcent);
                
                ByteBuffer b = new ByteBuffer();
                MemoryStream ms = new MemoryStream();
                newpb.WriteTo(ms);
                byte[] bytes = ms.ToArray();
                b.WriteShort((ushort)(bytes.Length + 2));
                b.WriteShort((ushort)MsgId.HeartBeatResponse);
                b.WriteBytes(bytes);
                byte[] msg = b.ToBytes();
                SendProtobuf(msg);
            } catch (Exception e) {
				Debug.Log("10 Socket: ShakeHands Error: "+ e);
			}
		}
        /* ------ 发送ping ------ */
        private void SendPing(byte[] data)
        {
            try
            {

                byte[] dataLength = data.Skip(0).Take(2).ToArray();
                byte[] dataId = data.Skip(2).Take(2).ToArray();
                byte[] dataConcent = data.Skip(data.Length - (data.Length - 4)).ToArray();
                Pb.PingRequest newpb1 = Pb.PingRequest.Parser.ParseFrom(dataConcent);
                Debug.Log("消息ID" + TempMsgID + "消息内容" + newpb1);
                ulong timpstamp = newpb1.Timestamp; 
                PingResponse lg = new Pb.PingResponse {  Timestamp= timpstamp };
                SocketManager socketManager = SocketManager.Instance;
                socketManager.SendProtobuf1(lg, MsgId.PingResponse);

            }
            catch (Exception e)
            {
                Debug.Log("10 Socket: ShakeHands Error: " + e);
            }
        }




        private void SendHeartPackage1 () {
			while(m_IsheartThreadRun) {
				Thread.Sleep(heartInterval);
                if(clientSocket != null && clientSocket.Connected && m_IsheartThreadRun)
				    SendPackage("{}");
			}
		}


        private void ProcessPackage() {
			
		}
		
		
		public void SetReceiveMessageDelegate (DelegateReceiveMessage arg) {
			receiveMessageDelegate = arg;
			sendQueueMessage();
		}
		
		public void SetDelegateDisconnect (DelegateDisconnect arg) {
			disconnectDelegate = arg;
		}
		
		private void sendQueueMessage ()
        {
			while (receiveMessageDelegate != null && packageQueue.Count > 0) {
				receiveMessageDelegate((byte[])packageQueue.Dequeue());
			}
		}

    }
}
